package LevelEditor;

import ClientGraphics.GraphicsMain;
import GameEngine.GameConstants.*;
import GameEngine.LevelInitializer;
import GameEngine.Vector;
import Objects.*;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.Rectangle2D;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JComponent;
import javax.swing.JScrollPane;
import javax.swing.JPanel;
import javax.swing.ScrollPaneConstants;
import org.dom4j.DocumentException;

/**
 * Panel showing a preview of the level.
 * 
 * The user can click here to place objects inside it.
 * 
 * @author jh20
 *
 */

public class LevelViewer extends JPanel {
	
	private JComponent levelDisplay;
	private JScrollPane scroll;
	
	private MainPanel mainPanel;
    private GraphicsMain gMain;
	
	private List<GameObject> levelObjects;
    private HashMap<GameObject, Thing> enumMap; // maps each object to its enum

    private short tempId = 0; // temporary IDs for the renderer

    // variables to handle special types of game objects
    private SpawnPoint spawnPoint; // makes sure there's only one spawn point
    private Vector firstClick = null; // for some resizable objects, we need this to keep track of the first click
	
	public LevelViewer(MainPanel mp, JComponent ld, GraphicsMain gm) {
		super();
		mainPanel = mp;
		
		levelDisplay = ld;
        gMain = gm;
        
		scroll = new JScrollPane(levelDisplay);
		scroll.setPreferredSize(new Dimension(EditorConstants.VIEWER_SIZE_X, EditorConstants.VIEWER_SIZE_Y));
		scroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
		scroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
		
		levelDisplay.addMouseListener(new LevelMouseListener());
		this.setLayout(new BorderLayout());
		this.add(scroll, BorderLayout.CENTER);
		
		// break up space some
		this.add(new JPanel(), BorderLayout.WEST);
		this.add(new JPanel(), BorderLayout.NORTH);
		this.add(new JPanel(), BorderLayout.SOUTH);
		this.add(new JPanel(), BorderLayout.EAST);

        levelObjects = new ArrayList<GameObject>();
        enumMap = new HashMap<GameObject, Thing>();
	}

    /**
     * Resets all of the data for the level
     */

    public void resetLevel() {
        levelObjects = new ArrayList<GameObject>();
        enumMap = new HashMap<GameObject, Thing>();
        spawnPoint = null;

        mainPanel.setLevelSize(mainPanel.getSizeX(), mainPanel.getSizeY());
        tempId = 0;
    }

    public void resetLevel(ArrayList<GameObject> newObjects, HashMap<GameObject, Thing> newEnum, SpawnPoint sp) {
        levelObjects = newObjects;
        enumMap = newEnum;
        spawnPoint = sp;

        mainPanel.setLevelSize(mainPanel.getSizeX(), mainPanel.getSizeY());
    }

    /**
     * Removes everything from the preview window
     */

    public void resetPreview() {
        List<BasicObject> updateObjects = new LinkedList<BasicObject>();
        for (GameObject obj : levelObjects) {
            obj.setExists(false);
            updateObjects.add(obj);
        }
        gMain.update(updateObjects);
    }

    public void setTempId(short i) {
        tempId = i;
    }

    /**
     * Removes objects after a resize
     * @param x New size of the level
     * @param y
     */

    public void resizeClear(int x, int y) {
        GameObject[] toRemove = new GameObject[levelObjects.size()];
        int i = 0;
        for (GameObject obj : levelObjects) {
            if (obj.getCenter().getX() >= x || obj.getCenter().getY() >= y) {
                // if the object is beyond the borders, remove it
                // we have to add it to a list of things to be removed to avoid ConcurrentModificationException
                toRemove[i] = obj;
                i++;
            }
        }

        // now remove

        for (int j = 0; j < i; j++) {
            enumMap.remove(toRemove[j]);
            levelObjects.remove(toRemove[j]);
        }
    }

    /**
     * Resets the first click
     */

    public void resetFirstClick() {
        firstClick = null;
    }

    /**
     * Delegation: Reads and writes to specified file by passing to xmlIO
     */

    public void write(String output) {
        try {
            XmlIO.write(output, levelObjects, enumMap, mainPanel);
            System.out.println("Saved.");
        } catch (IOException ex) {
            System.out.println("Could not write to file.");
        }
    }

    public void read(String input) {
        try {
            ArrayList<GameObject> newObjects = new ArrayList<GameObject>();
            HashMap<GameObject, Thing> newEnum = new HashMap<GameObject, Thing>();
            SpawnPoint newSpawn = null;
            XmlIO.read(input, newObjects, newEnum, newSpawn, mainPanel, gMain);
            this.resetLevel(newObjects, newEnum, newSpawn);
            System.out.println("Opened.");
        } catch (DocumentException ex) {
            System.out.println("Could not read from file.");
        } catch (FileNotFoundException ex) {
            System.out.println("Could not find file.");
        }
    }

    public SpawnPoint getSpawnPoint() {
        return spawnPoint;
    }

    public void setSpawnPoint(SpawnPoint spawnPoint) {
        this.spawnPoint = spawnPoint;
    }

	/**
	 * MouseListener for the viewer.
	 * @author jh20
	 *
	 */
	
	private class LevelMouseListener implements MouseListener {

		@Override
		public void mouseClicked(MouseEvent arg0) {

            if (arg0.getButton() == MouseEvent.BUTTON1) {
                // if left click, add object
                addObject(arg0);
                
            } else if (arg0.getButton() == MouseEvent.BUTTON3) {
                // if right click, remove object
                removeObject(arg0);
            }

            System.out.println("Current objects in level : " + levelObjects.size());
            repaint();

		}

		@Override
		public void mouseEntered(MouseEvent arg0) {
			
		}

		@Override
		public void mouseExited(MouseEvent arg0) {
			
		}

		@Override
		public void mousePressed(MouseEvent arg0) {
			
		}

		@Override
		public void mouseReleased(MouseEvent arg0) {
			
		}

        /**
         * Helper method; Adds an object to the list
         * @param arg0
         */

        public void addObject(MouseEvent arg0) {

            Vector size = null;
            Vector cen = null;
            Vector dir = EditorFunctions.getVector(mainPanel.getDir());

            if (EditorFunctions.isSpecialAdd(mainPanel.getCurrentType())) {
                // If it requires two clicks, we have a special case
                // first, has there been a first click?
                if (firstClick == null) {
                    // if not, this is the first click
                    firstClick = new Vector(arg0.getX(), arg0.getY());
                    return;
                } else {
                    // otherwise, this must be the second click
                    // we calculate the upperleft and bottomright corners based on the first and second clicks
                    // Vector dir = new Vector(EditorFunctions.normalize(new Vector(arg0.getX() - firstClick.getX(), arg0.getY() - firstClick.getY())));
                    cen = new Vector((firstClick.getX()+arg0.getX())/2, (firstClick.getY()+arg0.getY())/2);

                    double diffX = arg0.getX()-firstClick.getX();
                    double diffY = arg0.getY()-firstClick.getY();
                    size = new Vector(Math.sqrt(diffX*diffX + diffY*diffY), 40);

                    if (size.getX() <= 0) {
                        System.out.println("Double clicks are invalid for walls.");
                        // bad case
                        return;
                    }

                    dir = new Vector(diffX, diffY);
                    dir.scale(1/dir.getNorm());

                    // then reset firstClick
                    firstClick = null;
                }
            } else {
                size = LevelInitializer.getDefaultSize(mainPanel.getCurrentType(), dir);
                cen = new Vector(arg0.getX(), arg0.getY());
            }

            System.out.println(mainPanel.getCurrentType().toString() + " - " + arg0.getX() + ", " + arg0.getY() + " - " + mainPanel.getDir().name());

            GameObject newObject = GameEngine.LevelInitializer.objectConstructor(mainPanel.getCurrentType(), dir, cen, size);

            if (newObject != null) {

                // since spawn points are unique, we want to make sure that
                // we remove any previous ones

                if (newObject instanceof SpawnPoint) {
                    if (spawnPoint != null) {
                        this.remove(spawnPoint);
                    }
                    spawnPoint = (SpawnPoint) newObject;
                    newObject.setDir(new Vector(1,0));
                }
                levelObjects.add(newObject);
                enumMap.put(newObject, mainPanel.getCurrentType());

                newObject.setId(tempId);
                tempId++;

                List<BasicObject> updateObjects = new LinkedList<BasicObject>();
                updateObjects.add((BasicObject) newObject);
                
                /*
                List<BasicObject> updateObjects = new LinkedList<BasicObject>();
                for (GameObject obj: levelObjects) {
                    updateObjects.add(obj);
                }*/

                gMain.update(updateObjects);
            } else {
                System.out.println(mainPanel.getCurrentType().toString() + " is not supported.");
            }

        }
        
        /**
         * Helper method; removes an object
         * @param arg0
         */

        public void removeObject(MouseEvent arg0) {

                // iterate backwards over the list

                for (int i = levelObjects.size() - 1; i >=0; i--) {
                    GameObject obj = levelObjects.get(i);
                    if (obj.contains(arg0.getX(), arg0.getY())) {
                        // if the current object's hitbox contains the mouse click

                        // remove the object
                        this.remove(obj);

                        // then break
                        break;
                    }
                }
        }

        public void remove(GameObject obj) {
            enumMap.remove(obj);

            levelObjects.remove(obj);

            obj.setExists(false);


            List<BasicObject> updateObjects = new LinkedList<BasicObject>();
            updateObjects.add((BasicObject) obj);

            gMain.update(updateObjects);
        }
		
	}

}
